package polimi.reds.optimizer.test;

import java.util.ArrayList;


import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;


import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import polimi.reds.optimizer.OptimizerStrategyOptions;

public class SimulationEnvironment {
	
	public final static String PARAM_NUM_NODES = "num_nodes";
	public final static String SUBSCRIBERS_PERCENTAGE = "subscribers_percentage";
	public final static String PUBLISHERS_PERCENTAGE = "publishers_percentage";
	//public final static String DURATION = "duration";
	//public final static String CHANGE_MESSAGE_TRAFFIC = "change_message_traffic";
	//public final static String CHANGE_SUBSCRIPTIONS_TRAFFIC = "change_subscriptions_traffic";
	//public final static String NOISE = "noise";
	//public final static String GAUSSIAN_STDEV = "gaussian_stdev";
	//public final static String RESULTS_FILE = "results_file";
	//public final static String TOPOLOGY = "topology";	
	//public final static String TOPOLOGY_SCALEFREE = "scalefree";
	//public final static String TOPOLOGY_SPIRAL = "spiral";
	
	//public final static String STAT_CURRENT_STEP = "step";
	//public final static String STAT_PERFORMED_REWIRES = "performed_rewires";
	//public final static String STAT_EXCHANGED_MESSAGES = "exchanged_messages";

	private OptimizerStrategyOptions o;
	
	public static void main(String[] args) {
		
		//o.addOption(CHANGE_MESSAGE_TRAFFIC, "change message traffic after each step", false);
		//o.addOption(CHANGE_SUBSCRIPTIONS_TRAFFIC, "change subscriptions traffic after each step", false);
		//o.addOption(NOISE, "add the given threshold of randomness", 0);
		//o.addOption(GAUSSIAN_STDEV, "in multi-filter consider only the most probable filter (using a normal distribution with mean max_filter_size/2 and this stdev)", 0);
		//o.addOption(RESULTS_FILE, "csv file where to save results", "csvprova.csv");
		//o.addOption(TOPOLOGY, "initial topology", TOPOLOGY_SCALEFREE);
		
		OptimizerStrategyOptions o = new OptimizerStrategyOptions();
		o.addOption(PARAM_NUM_NODES, "number of nodes", 100);
		o.addOption(SUBSCRIBERS_PERCENTAGE, "number of subscribers", 500); 
		o.addOption(PUBLISHERS_PERCENTAGE, "number of publishers", 200);
		//o.addOption(DURATION, "duration of the simulation in ms", 100000);
		
		o.addOption(Broker.PARAM_SEED, "random seed", 124);	
		o.addOption(Broker.PARAM_TOTAL_MESSAGE_CLASSES, "number of message classes", 1);
		o.addOption(Broker.PARAM_MESSAGE_CLASSES_PER_SUBSCRIPTION, "message classes per subscription", 1);
		o.addOption(Broker.PARAM_MESSAGES_PER_BROKER, "total number of messages per broker", 100);
		o.addOption(Broker.PARAM_SUBSCRIPTIONS_PER_BROKER, "total number of subscriptions per broker", 1);
		o.addOption(Broker.PARAM_ALGORITHM, "optimization algorithm name", "SATOptimizerStrategy");
		o.addOption(Broker.PARAM_ALGORITHM_PARAMETERS, "algorithm parameters", "threshold=0,maxneighbors=5,updateevents=100");
		o.addOption(Broker.PARAM_BASEPORT, "base port for brokers", 10000);
		o.addOption(Broker.PARAM_ADDRESS, "ip address of the brokers", "127.0.0.1");
		o.addOption(Broker.PARAM_COMPONENTS_PER_BROKER, "components per broker",1);
		
		// Parse the command line options
		try {
			o.parse(args);
		} catch (Exception e) {
			System.out.println("Error: " + e.getMessage());
			System.out.println("For usage information use -help");
			System.exit(0);
		}
		
		SimulationEnvironment simulator = new SimulationEnvironment(o);
		simulator.simulate();
	}
	
	public SimulationEnvironment(OptimizerStrategyOptions o) {
		this.o = o;
	}
	
	public void simulate() {		
		
		int numberOfBrokers=o.getIntegerOption(PARAM_NUM_NODES);
		//int simulationDuration=o.getIntegerOption(DURATION);
		int seed=o.getIntegerOption(Broker.PARAM_SEED);
		Random random = seed==0 ? new Random() : new Random(seed);
		
		List<Broker> brokers = new ArrayList<Broker>();
		
		Logger.getLogger("polimi.reds.broker.overlay.TopologyManager").setLevel(Level.WARNING);
		Logger.getLogger("polimi.reds.optimizer.AbstractOverlayOptimizer").setLevel(Level.ALL);
		//Logger.getLogger("polimi.reds.optimizer.test.Broker").setLevel(Level.FINE);
		
		int nsubscribers = numberOfBrokers*o.getIntegerOption(SUBSCRIBERS_PERCENTAGE)/100;
		int npublishers = numberOfBrokers*o.getIntegerOption(PUBLISHERS_PERCENTAGE)/100;
		List<Boolean> subscribersMask = new ArrayList<Boolean>();
		List<Boolean> publishersMask = new ArrayList<Boolean>();
		
		for (int i=0; i<numberOfBrokers; i++) {
			if (i<nsubscribers) {
				subscribersMask.add(true);
			} else {
				subscribersMask.add(false);				
			}
			
			if (i<npublishers) {
				publishersMask.add(true);
			} else {
				publishersMask.add(false);
			}
		}
		
		Collections.shuffle(subscribersMask, random);
		Collections.shuffle(publishersMask, random);
		
		System.out.println("Creating Brokers...");
		OptimizerStrategyOptions myOptions=null;
		for (int i=0; i<numberOfBrokers; i++) {
			myOptions = o.clone();
			if (!publishersMask.get(i)) {
				myOptions.addOption(Broker.PARAM_MESSAGES_PER_BROKER, "total number of messages per broker (per burst)", 0);
			}
			
			if (!subscribersMask.get(i)) {
				myOptions.addOption(Broker.PARAM_SUBSCRIPTIONS_PER_BROKER, "total number of subscriptions per broker (per burst)", 0);
			}
			
			brokers.add(new Broker(myOptions));
		}
		
		System.out.println("Creating Broker Topology...");
		
		List<Edge> edges = (new ScaleFreeTopology(brokers,random)).createTopology();
		
		for (Edge e:edges) {
			e.getNode1().connectTo(e.getNode2());
		}
		
		System.out.println("Initializing brokers...");
		for (Broker b:brokers) {
			b.start();
		}
		
		System.out.println("Starting simulation...");
		int waittime=1000;
		int step=0;
		boolean endSimulation = true;
		Map<String,Double> totalStatsOld;
		Map<String,Double> totalStatsNew = new HashMap<String,Double>();
		do {
			totalStatsOld=new HashMap<String,Double>(totalStatsNew);
			totalStatsNew = new HashMap<String,Double>();
			try {
				Thread.sleep(waittime);
			} catch (InterruptedException e) {
			}
			endSimulation = true;
			step++;
			System.out.println("*** Topology step=" + step + "***");
			int nlinks = 0;
			
			for (Broker broker:brokers) {
				
				//endSimulation = endSimulation && !broker.isAlive();
				Map<String,Double> stats = broker.getStats();
				Double stat;
			
				for (String s:stats.keySet()) {
					stat = totalStatsNew.get(s);
					if (stat == null) {
						stat = 0.0;
					}
					stat += stats.get(s);
					totalStatsNew.put(s, stat);
				}
				
				broker.printNeighborHood();
				nlinks += broker.getNumberOfLinks();
			}
			nlinks /= 2;
			totalStatsNew.put("links", new Double(nlinks));
			List<String> statsSet = new ArrayList<String>(totalStatsNew.keySet());
			Collections.sort(statsSet);
			for (String s:statsSet) {
				System.out.println(s + ": " + Broker.formatStat(totalStatsNew.get(s)));
				endSimulation = endSimulation && totalStatsNew.get(s).equals(totalStatsOld.get(s));
			}
			System.out.println("*** End of Topology ***");
			
			
		} while (!endSimulation);
				
		//System.out.println("Terminating brokers...");
		//for (Broker b:brokers) {
		//	b.stop();
		//}
		//
		//boolean waitingForBroker;
		//do {
		//	waitingForBroker=false;
		//	for (Broker broker:brokers) {
		//		waitingForBroker = waitingForBroker || broker.isBusy();
		//	}
		//} while (waitingForBroker);
		
		System.out.println("Simulation complete.");
		
		System.out.println("Bad messages analysis: ");
		boolean bad=false;
		for (Integer i=0; i<Broker.msgSeqNumber; i++) {
			if (Broker.messages.get(i) == null) {
				System.out.println("Message " + i + " undelivered :(");
				bad=true;
			} else if ((Broker.messages.get(i) != 9)) {
				System.out.println("Message " + i + " has been received " + Broker.messages.get(i) + " times :(");
				bad=true;
			}
		}
		if (!bad) {
			System.out.println("All messages have been received nine times.");
		}
		
		System.exit(0);
	}
	

}